package com.zbkj.crmeb.task.plat.poizon;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.beust.jcommander.internal.Maps;
import com.crmeb.core.utils.RedisUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.utils.DateUtil;
import com.zbkj.crmeb.task.plat.poizon.dto.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Zhangxuyang
 * @date 4/22/2024 9:06 上午
 */
@Component
@Slf4j
@Data
public class PoizonApi implements ApplicationRunner {
//    private static final String URL_TEST = "https://i-mf1-openapi.poizon.net";
//    private static final String URL_ONLINE = "https://openapi.poizon.com";
//    private static final String URL = URL_ONLINE;
//    private static String tenantName = "lavita";
//    private static String userName = "lavita";
//    private static String userPwd = "abc1234_";
//    private static String loginToken = "194ae596641645858f05aa2f81c2ae1a";

    @Autowired
    RedisUtil redisUtil;
    @Autowired
    Redisson redisson;
    @Value("${poizon.URL}")
    private String URL;
    private static String tenantName;

    @Value("${poizon.tenantName}")
    public void setTenantName(String tenantName) {
        PoizonApi.tenantName = tenantName;
    }

    @Value("${poizon.userName}")
    private String userName;
    @Value("${poizon.userPwd}")
    private String userPwd;

    private static String loginToken;

    @Value("${poizon.loginToken}")
    public void setLoginToken(String loginToken) {
        PoizonApi.loginToken = loginToken;
    }

//    private static String accessToken;
//    private static String refreshToken;

    private static String POIZON_TOKEN_INFO_KEY = "PoizonTokenInfo";
    private static String POIZON_UPDATE_TOKEN_KEY = "PoizonUpdateToken";

    public PoizonApi() {
//        checkAndUpdateToken();
    }

    /**
     * 登录
     *
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.LoginDataDTO> doLogin() {
        String url = URL + "/login/loginUser";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        ToizonLoginUserDto dto = new ToizonLoginUserDto();
        dto.setTenantName(tenantName);
        dto.setUserName(userName);
        dto.setUserPwd(userPwd);
        String params = JSON.toJSONString(dto);
        headers.put("login_token", Collections.singletonList(loginToken));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), loginToken, tenantName)));
        log.info("doLogin 获取token 请求 {}，{}，{}",url, JSON.toJSONString(params), JSON.toJSONString(headers));
        String responseBody = post(url, params, headers);
        log.info("refreshToken 获取token 响应 {}", responseBody);
        PoizonResponseBodyDto<PoizonResponseBodyDto.LoginDataDTO> responseBodyDto = JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.LoginDataDTO>>() {
        });
        if (responseBodyDto.getStatus() == 200) {
            PoizonTokenInfoDto tokenInfo = getTokenInfo();
            tokenInfo.setAccessToken(responseBodyDto.getData().getAccessToken());
            tokenInfo.setRefreshToken(responseBodyDto.getData().getRefreshToken());
            tokenInfo.setRefreshTokenUpdateTime(DateUtil.nowDateTime());
            tokenInfo.setAccessTokenUpdateTime(DateUtil.nowDateTime());
            saveTokenInfo(tokenInfo);
        }
        log.info("得物账号登录结果：{},", JSON.toJSONString(responseBodyDto));
        return responseBodyDto;
    }

    /**
     * 登录
     *
     * @param dto params
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.LoginDataDTO> doLogin(ToizonLoginUserDto dto) {
        String url = URL + "/login/loginUser";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = JSON.toJSONString(dto);
        headers.put("login_token", Collections.singletonList(loginToken));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), loginToken, tenantName)));
        String responseBody = post(url, params, headers);
        PoizonResponseBodyDto<PoizonResponseBodyDto.LoginDataDTO> responseBodyDto = JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.LoginDataDTO>>() {
        });
        if (responseBodyDto.getStatus() == 200) {
            PoizonTokenInfoDto tokenInfo = getTokenInfo();
            tokenInfo.setAccessToken(responseBodyDto.getData().getAccessToken());
            tokenInfo.setRefreshToken(responseBodyDto.getData().getRefreshToken());
            tokenInfo.setRefreshTokenUpdateTime(DateUtil.nowDateTime());
            tokenInfo.setAccessTokenUpdateTime(DateUtil.nowDateTime());
            saveTokenInfo(tokenInfo);
        }
        return responseBodyDto;
    }

    /**
     * 刷新Token
     *
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.RefreshTokenDataDTO> refreshToken() {
        log.info("refreshToken 入口");
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        log.info("tokenInfo {}", JSON.toJSONString(tokenInfo));
        // 如果首次上线 不存在Redis中 直接报错
        if (StringUtils.isBlank(tokenInfo.getRefreshToken())
                || StringUtils.isBlank(tokenInfo.getAccessToken()) ) {
            PoizonResponseBodyDto<PoizonResponseBodyDto.RefreshTokenDataDTO> res = new PoizonResponseBodyDto<>();
            res.setCode(-1L);
            res.setStatus(0);
            res.setMsg("Redis数据不存在，先登录");
            log.info("Redis数据不存在，先登录");
            return res;
        }
        // 如果AccessToken距离上一次刷新小于30分钟就返回 redis AccessToken
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(tokenInfo.getAccessTokenUpdateTime());
        calendar.add(Calendar.MINUTE,30);
        if (DateUtil.compareDate(calendar.getTime(),DateUtil.nowDateTime())) {
            PoizonResponseBodyDto<PoizonResponseBodyDto.RefreshTokenDataDTO> res = new PoizonResponseBodyDto<>();
            res.setCode(200L);
            res.setStatus(200);
            res.setMsg("无需更新");
            PoizonResponseBodyDto.RefreshTokenDataDTO data = new PoizonResponseBodyDto.RefreshTokenDataDTO();
            data.setAccessToken(tokenInfo.getAccessToken());
            res.setData(data);
            log.info("无需更新");
            return res;
        }

        String url = URL + "/login/refreshToken";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        PoizonRefreshTokenDto dto = new PoizonRefreshTokenDto();
        dto.setTenantName(tenantName);
        String params = JSON.toJSONString(dto);
        headers.put("refresh_token", Collections.singletonList(tokenInfo.getRefreshToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getRefreshToken(), tenantName)));
        log.info("refreshToken 获取token 请求 {}，{}，{}",url, JSON.toJSONString(params), JSON.toJSONString(headers));
        String responseBody = post(url, params, headers);
        log.info("refreshToken 获取token 响应 {}", responseBody);
        PoizonResponseBodyDto<PoizonResponseBodyDto.RefreshTokenDataDTO> responseBodyDto = JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.RefreshTokenDataDTO>>() {
        });
        if (null != responseBodyDto.getStatus() && responseBodyDto.getStatus() == 200) {
            tokenInfo.setAccessToken(responseBodyDto.getData().getAccessToken());
            tokenInfo.setAccessTokenUpdateTime(DateUtil.nowDateTime());
            saveTokenInfo(tokenInfo);
        }
        return responseBodyDto;
    }

    /**
     * 刷新Token
     *
     * @param dto params
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.RefreshTokenDataDTO> refreshToken(PoizonRefreshTokenDto dto) {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/login/refreshToken";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = JSON.toJSONString(dto);
        headers.put("refresh_token", Collections.singletonList(tokenInfo.getRefreshToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getRefreshToken(), tenantName)));
        String responseBody = post(url, params, headers);
        PoizonResponseBodyDto<PoizonResponseBodyDto.RefreshTokenDataDTO> responseBodyDto = JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.RefreshTokenDataDTO>>() {
        });
        if (responseBodyDto.getStatus() == 200) {
            tokenInfo.setAccessTokenUpdateTime(DateUtil.nowDateTime());
            tokenInfo.setAccessToken(responseBodyDto.getData().getAccessToken());
            saveTokenInfo(tokenInfo);
        }
        return responseBodyDto;
    }

    /**
     * 推送商品（增量）
     *
     * @param dto params
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.PushStockUpdateDataDTO> pushIncrement(PoizonPushStockUpdateDto dto) {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/commodity/pushIncrement";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = JSON.toJSONString(dto);
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getAccessToken(), tenantName)));
        String responseBody = post(url, params, headers);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.PushStockUpdateDataDTO>>() {
        });
    }

    /**
     * 更新库存&价格
     *
     * @param dto params
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.UpdateStocksAndPricesDataDTO> updateStocksAndPrices(PoizonUpdateStocksAndPricesDto dto) {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/commodity/updateSku";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = JSON.toJSONString(dto);
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getAccessToken(), tenantName)));
        String responseBody = post(url, params, headers);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.UpdateStocksAndPricesDataDTO>>() {
        });
    }

    /**
     * 获取全量商品列表
     *
     * @param dto params
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.SkuListDataDTO> getSkuList(ToizonGetSkuListDto dto) {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/commodity/getSkuList";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = JSON.toJSONString(dto);
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getAccessToken(), tenantName)));
        String responseBody = post(url, params, headers);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.SkuListDataDTO>>() {
        });
    }

    /**
     * 拉取订单
     *
     * @param dto params
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.RetrieveOrdersDataDTO> orderInterface(PoizonRetrieveOrdersDto dto) {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/order/pull";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = JSON.toJSONString(dto);
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getAccessToken(), tenantName)));
        log.info("爬取得物订单数据请求：{},{},{}", url, JSON.toJSONString(params),JSON.toJSONString(headers));
        String responseBody = post(url, params, headers);
        log.info("爬取得物订单数据响应：{}", responseBody);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.RetrieveOrdersDataDTO>>() {
        });
    }

    /**
     * 确认订单
     *
     * @param dto params
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.OrderConfirmDataDTO> orderConfirm(ToizonOrderConfirmDto dto) {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/order/confirm";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = JSON.toJSONString(dto);
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getAccessToken(), tenantName)));
        String responseBody = post(url, params, headers);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.OrderConfirmDataDTO>>() {
        });
    }

    /**
     * 取消订单
     *
     * @param dto params
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.OrderCancelDataDTO> orderCancel(ToizonOrderCancelDto dto) {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/order/cancel";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = JSON.toJSONString(dto);
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getAccessToken(), tenantName)));
        String responseBody = post(url, params, headers);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.OrderCancelDataDTO>>() {
        });
    }

    /**
     * 取消订单原因Enum
     *
     * @return
     */
    public PoizonResponseBodyDto<List<PoizonResponseBodyDto.OrderCancelReasonEnum>> orderCancelReasonEnum() {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/order/enum/cancel/reasonEnum";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = "{}";
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getAccessToken(), tenantName)));
        String responseBody = post(url, params, headers);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<List<PoizonResponseBodyDto.OrderCancelReasonEnum>>>() {
        });
    }

    /**
     * 查询订单信息
     *
     * @param dto params
     * @return
     */
    public PoizonResponseBodyDto<PoizonResponseBodyDto.QueryOrderInfoDataDTO> queryOrderInfo(ToizonQueryOrderInfoDto dto) {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/order/queryOrderInfo";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = JSON.toJSONString(dto);
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getAccessToken(), tenantName)));
        String responseBody = post(url, params, headers);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<PoizonResponseBodyDto.QueryOrderInfoDataDTO>>() {
        });
    }

    /**
     * 获取品牌列表
     *
     * @return List
     */
    public PoizonResponseBodyDto<List<PoizonResponseBodyDto.BrandDataDTO>> getBrandList() {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/commodity/getBrandList";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = "{}";
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(null), tokenInfo.getAccessToken(), tenantName)));
        log.info("获取品牌列表请求：{},{},{}", url, JSON.toJSONString(params),JSON.toJSONString(headers));
        String responseBody = post(url, null, headers);
        log.info("获取品牌列表响应：{}", responseBody);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<List<PoizonResponseBodyDto.BrandDataDTO>>>() {
        });
    }

    /**
     * 获取类目列表
     *
     * @return List
     */
    public PoizonResponseBodyDto<List<PoizonResponseBodyDto.CategoryDataDTO>> getCategoryList() {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/commodity/getCategoryList";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = "{\"level\":3}";
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getAccessToken(), tenantName)));
        log.info("获取类目列表 请求：{},{},{}", url, JSON.toJSONString(params),JSON.toJSONString(headers));
        String responseBody = post(url, params, headers);
        log.info("获取类目列表 响应：{}", responseBody);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<List<PoizonResponseBodyDto.CategoryDataDTO>>>() {
        });
    }

    /**
     * 获取性别列表
     *
     * @return List
     */
    public PoizonResponseBodyDto<List<PoizonResponseBodyDto.GenderDataDTO>> getGenderList() {
        PoizonTokenInfoDto tokenInfo = getTokenInfo();
        String url = URL + "/boutique/commodity/getGenderList";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String params = "{}";
        headers.put("access_token", Collections.singletonList(tokenInfo.getAccessToken()));
        headers.put("sign", Collections.singletonList(createSignStr(parseMap(params), tokenInfo.getAccessToken(), tenantName)));
        log.info("获取性别列表 请求：{},{},{}", url, JSON.toJSONString(params),JSON.toJSONString(headers));
        String responseBody = post(url, params, headers);
        log.info("获取性别列表 响应：{}", responseBody);
        return JSON.parseObject(responseBody, new TypeReference<PoizonResponseBodyDto<List<PoizonResponseBodyDto.GenderDataDTO>>>() {
        });
    }

    private PoizonTokenInfoDto getTokenInfo() {
        String jsonStr = (String) redisUtil.get(POIZON_TOKEN_INFO_KEY);
        if (StringUtils.isBlank(jsonStr)) {
            return new PoizonTokenInfoDto();
        }
        return JSON.parseObject(jsonStr, PoizonTokenInfoDto.class);
    }

    private void saveTokenInfo(PoizonTokenInfoDto dto) {
        redisUtil.set(POIZON_TOKEN_INFO_KEY, JSON.toJSONString(dto));
    }

    public static String post(String url, String params, MultiValueMap<String, String> headers) {
        return request(url, params, headers, HttpMethod.POST, MediaType.APPLICATION_JSON);
    }

    /**
     * http请求
     *
     * @param url
     * @param params    请求参数
     * @param headers   请求头
     * @param method    请求方式
     * @param mediaType 参数类型
     * @return
     */
    public static String request(String url, Object params, MultiValueMap<String, String> headers, HttpMethod method, MediaType mediaType) {
        if (url == null || url.trim().isEmpty()) {
            return null;
        }
        RestTemplate client = new RestTemplate();
        // header
        HttpHeaders httpHeaders = new HttpHeaders();
        if (headers != null) {
            httpHeaders.addAll(headers);
        }
        // 提交方式：表单、json
        httpHeaders.setContentType(mediaType);
        HttpEntity<Object> httpEntity = new HttpEntity<>(params, httpHeaders);
        ResponseEntity<String> response = client.exchange(url, method, httpEntity, String.class);
        return response.getBody();
    }

    public static Map<String, Object> parseMap(String json) {
        Map<String, Object> parameterMap = Maps.newHashMap();
        if (StringUtils.isNotBlank(json)) {
            try {
                Map<String, Object> map = new ObjectMapper().readValue(json, new com.fasterxml.jackson.core.type.TypeReference<HashMap<String, Object>>() {
                });
                map.forEach((key, value) -> {
                    if (loginToken.equals(key)) {
                        return;
                    }
                    parameterMap.put(key, value);
                });
            } catch (Exception e) {
                log.error("parseMap error, json={}", json, e);
            }
        }
        return parameterMap;
    }

    public static String createSignStr(Map<String, Object> map, String token,
                                       String tenantName) {
        String signStr = map.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .map(entry -> entry.getKey() + "=" + entry.getValue())
                .collect(Collectors.joining("&"));
        return md5(signStr + token + tenantName);
    }

    public static String md5(String str) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            log.error("Md5Util Exception: ", e);
        }
        assert md != null;
        byte[] md5Bytes = md.digest(str.getBytes(StandardCharsets.UTF_8));
        return Hex.encodeHexString(md5Bytes);
    }

    @Scheduled(initialDelay = 1000L * 60 * 30, fixedDelay = 1000L * 60 * 30)
    public void checkAndUpdateToken() {
        String logID = UUID.randomUUID().toString();
        log.info("logID:[{}]", logID);
        log.info("[{}] checkAndUpdateToken 入口", logID);

        RLock rLock = redisson.getLock(POIZON_UPDATE_TOKEN_KEY);
        try {
            if (!rLock.tryLock(3, TimeUnit.SECONDS)) {
                log.warn("[{}] 未获取到锁，稍后尝试", logID);
                return;
            }
            // refresh_token 有效期30天
            // access_token 有效期4小时
            log.info("[{}] 得物刷新token", logID);
            //
            PoizonResponseBodyDto<PoizonResponseBodyDto.RefreshTokenDataDTO> responseBodyDto = refreshToken();
            log.info("[{}] refreshToken 后", logID);
            if (null == responseBodyDto.getStatus() || 200 != responseBodyDto.getStatus()) {
                log.info("[{}] 执行 doLogin", logID);
                doLogin();
            }
        } catch (Exception ignored) {
        } finally {
            rLock.unlock();
        }
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        checkAndUpdateToken();
    }
}
